package com.netx.generics.util;
import com.netx.generics.basic.Checker;
import com.netx.generics.basic.Context;
import com.netx.generics.basic.Property;
import com.netx.generics.basic.IntegrityException;
import com.netx.generics.basic.ObjectAlreadyExistsException;
import com.netx.generics.translation.Results;
import com.netx.generics.translation.Translator;
import com.netx.generics.translation.Message;
import com.netx.generics.translation.MessageFormatter;
import com.netx.generics.time.Moment;
import com.netx.generics.time.Date;
import com.netx.generics.time.Time;
import java.util.Map;
import java.util.HashMap;
import java.text.SimpleDateFormat;


public class Builtins {

	private Builtins() {
	}

	private static final Map<String,BuiltinCall> _functions;
	
	static {
		_functions = new HashMap<String,BuiltinCall>();
		registerCall(new NowCall());
		registerCall(new DateCall());
		registerCall(new TimeCall());
		registerCall(new ContextCall());
		registerCall(new ExprCall());
	}

	public static void registerCall(BuiltinCall call) throws ObjectAlreadyExistsException {
		Checker.checkNull(call, "call");
		if(_functions.get(call.getFunctionName()) != null) {
			throw new ObjectAlreadyExistsException(call.getFunctionName());
		}
		else {
			_functions.put(call.getFunctionName(), call);
		}
	}

	public static String evaluate(String expression) {
		Checker.checkNull(expression, "expression");
		BuiltinScanner scanner = new BuiltinScanner(expression);
		BuiltinParser parser = new BuiltinParser(scanner);
		BuiltinAnalyzer analyzer = new BuiltinAnalyzer(parser);
		// fool the compiler:
		analyzer.toString();
		Results results = new Translator(scanner).performWork();
		if(results.hasErrors()) {
			String step = null;
			switch(results.getTranslator().getPerformedStepCount()) {
			case 1:
				step = "scan";
				break;
			case 2:
				step = "parse";
				break;
			case 3:
				step = "semantic";
				break;
			default:
				throw new IntegrityException(results.getTranslator().getPerformedStepCount());
			}
			throw new BuiltinException(results.getMessages(step+" errors found in expression", new BuiltinErrorFormatter()));
		}
		else {
			return (String)results.getContents();
		}
	}
	
	// for BuiltinAnalyzer:
	static String call(String functionName, String[] args) throws BuiltinCallException {
		BuiltinCall bCall = _functions.get(functionName);
		if(bCall == null) {
			throw new BuiltinCallException(functionName, "unknown function");
		}
		else {
			boolean argsAccepted = false;
			for(int numArgs : bCall.acceptArguments()) {
				if(numArgs == args.length) {
					argsAccepted = true;
				}
			}
			if(!argsAccepted) {
				throw new BuiltinCallException(functionName, "illegal number of arguments: "+args.length);
			}
			try {
				return bCall.call(args);
			}
			catch(Exception e) {
				throw new BuiltinCallException(bCall.getFunctionName(), e);
			}
		}
	}
	
	private static class BuiltinErrorFormatter implements MessageFormatter {
		
		public BuiltinErrorFormatter() {
		}

		public String format(Message m) {
			if(m.getPosition() == null) {
				return m.getMessage();
			}
			else {
				return m.getMessage()+" (char "+m.getPosition().getIndex()+")";
			}
		}
	}
	
	private static class NowCall implements BuiltinCall {
		
		public String getFunctionName() {
			return "now";
		}
		
		public int[] acceptArguments() {
			return new int[] {0, 1};
		}
		
		public String call(String[] args) {
			Moment now = new Moment();
			if(args.length == 0) {
				return now.format();
			}
			else {
				try {
					return now.format(new SimpleDateFormat(args[0]));
				}
				catch(IllegalArgumentException iae) {
					throw new IllegalArgumentException("invalid date/time format used as input: '"+args[0]+"'");
				}
			}
		}
	}

	private static class DateCall implements BuiltinCall {
		
		public String getFunctionName() {
			return "date";
		}
		
		public int[] acceptArguments() {
			return new int[] {0, 1};
		}

		public String call(String[] args) {
			Date date = new Moment().getDate();
			if(args.length == 0) {
				return date.format();
			}
			else {
				try {
					return date.format(new SimpleDateFormat(args[0]));
				}
				catch(IllegalArgumentException iae) {
					throw new IllegalArgumentException("invalid date format used as input: '"+args[0]+"'");
				}
			}
		}
	}

	private static class TimeCall implements BuiltinCall {
		
		public String getFunctionName() {
			return "time";
		}
		
		public int[] acceptArguments() {
			return new int[] {0, 1};
		}

		public String call(String[] args) {
			Time time = new Moment().getTime();
			if(args.length == 0) {
				return time.format();
			}
			else {
				try {
					return time.format(new SimpleDateFormat(args[0]));
				}
				catch(IllegalArgumentException iae) {
					throw new IllegalArgumentException("invalid time format used as input: '"+args[0]+"'");
				}
			}
		}
	}
	
	private static class ContextCall implements BuiltinCall {
		
		public String getFunctionName() {
			return "context";
		}
		
		public int[] acceptArguments() {
			return new int[] {1};
		}

		public String call(String[] args) {
			Property p = Context.getRootContext().getProperty(args[0]);
			return p==null ? "" : p.stringValue();
		}
	}

	private static class ExprCall implements BuiltinCall {
		public String getFunctionName() {
			return "expr";
		}
		
		public int[] acceptArguments() {
			return new int[] {1};
		}

		public String call(String[] args) {
			StringBuilder sb = new StringBuilder();
			for(String s : args) {
				sb.append(s);
			}
			return sb.toString();
		}
	}

}
